package nirvana.core.validation;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.groups.Default;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * validate utils
 *
 * @author linkanyway
 * @date 2021/12/05 18:47
 * @since 0.1.0
 */
public final class ValidatorUtils {

    /**
     * init capacity for map used to store validate messages
     */
    private static final Integer INIT_CAPACITY = 10;


    /**
     * @see Validator
     * validator
     */
    private static final Validator VALIDATOR;

    //static block
    static {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory ();
        VALIDATOR = factory.getValidator ();
    }

    /**
     * private constructor
     */
    private ValidatorUtils() {

    }

    /**
     * validate object
     *
     * @param obj the object need to be valid
     * @param <T> the type of the object that ready to be valid
     * @return validation result
     * @see ValidationResult
     * * @throws NullPointerException the object to valid can't be null
     */
    public static <T> ValidationResult validate(T obj) {
        if (Objects.isNull (obj)) {
            throw new NullPointerException ("the object to valid can't be null");
        }
        // parse the validation result
        Set<ConstraintViolation<T>> validateSet = VALIDATOR.validate (obj, Default.class);
        return buildValidationResult (validateSet);
    }


    /**
     * validate field/property
     * note: the field must  have getter method
     *
     * @param obj          the object ready to valid
     * @param propertyName the property/field name ready to valid of the object instance
     * @param <T>          object type
     * @return validate result
     * @throws NullPointerException the objec
     *                              t to valid can't be null
     * @see Validator
     * @see ValidationResult
     */
    public static <T> ValidationResult validateProperty(T obj, @NotNull String propertyName) {
        if (Objects.isNull (obj)) {
            throw new NullPointerException ("the object to valid can't be null");
        }
        ValidationResult result = new ValidationResult ();


        Set<ConstraintViolation<T>> set = VALIDATOR.validateProperty (obj, propertyName, Default.class);

        if (!CollectionUtils.isEmpty (set)) {
            result.setError (true);
            Map<String, String> errorMsg = new HashMap<> (INIT_CAPACITY);
            for (ConstraintViolation<T> cv : set) {
                errorMsg.put (propertyName, cv.getMessage ());
            }
            result.setErrorMsg (errorMsg);
        }
        return result;
    }


    /**
     * convert to validation result
     *
     * @param validateSet validation constraint violation collection
     * @param <T>         type
     * @return result
     * @throws NullPointerException parameter cant ben null
     * @see ConstraintViolation
     * @see ValidationResult
     */
    private static <T> ValidationResult buildValidationResult(@NotNull Set<ConstraintViolation<T>> validateSet) {
        if (validateSet == null) {
            throw new NullPointerException ("parameter can't be null");
        }
        ValidationResult validationResult = new ValidationResult ();
        if (!CollectionUtils.isEmpty (validateSet)) {
            validationResult.setError (true);
            Map<String, String> errorMsgMap = new HashMap<> (INIT_CAPACITY);
            for (ConstraintViolation<T> constraintViolation : validateSet) {
                errorMsgMap.put (constraintViolation.getPropertyPath ().toString (), constraintViolation.getMessage ());
            }
            validationResult.setErrorMsg (errorMsgMap);
        }
        return validationResult;
    }
}
